#include "abb.h"
#include "pila.h"
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

/* ******************************************************************
 *                        PRUEBAS UNITARIAS							*
 * *****************************************************************/
 
/* Función auxiliar para imprimir si estuvo OK o no. */
void print_test (char* name, bool result) {
    printf("%s: %s\n", name, result? "OK" : "ERROR");
}

void prueba_crear_abb_vacio () {
	printf("\nPRUEBA CREAR ABB VACIO:\n\n");
	abb_t *ab = abb_crear (NULL,NULL);
	
	print_test("Prueba ABB crear ABB vacio", ab);
	print_test("Prueba ABB la cantidad de elementos es 0", abb_cantidad(ab) == 0);
	print_test("Prueba ABB obtener clave A, es NULL, no existe", !abb_obtener(ab, "A"));
	print_test("Prueba ABB pertenece clave A es false", !abb_pertenece(ab, "A"));
	print_test("Prueba ABB borrar clave A, es NULL, no existe", !abb_borrar(ab, "A"));

	abb_destruir(ab);
}

void prueba_insertar () {
	printf("\nPRUEBA INSERTAR:\n\n");
	abb_t* ab = abb_crear(strcmp,NULL);

	char *claves[] = {"perro", "gato", "vaca"};
	char *valores[] = {"guau", "miau", "mu"};

	/* Inserta 1 valor y luego lo borra */
	print_test("Prueba ABB insertar claves[0]", abb_guardar(ab, claves[0], valores[0]));
	print_test("Prueba ABB la cantidad de elementos es 1", abb_cantidad(ab) == 1);
	print_test("Prueba ABB obtener claves[0] es valores[0]", abb_obtener(ab, claves[0]) == valores[0]);
	print_test("Prueba ABB obtener claves[0] es valores[0]", abb_obtener(ab, claves[0]) == valores[0]);
	print_test("Prueba ABB borrar claves[0], es valores[0]", abb_borrar(ab, claves[0]) == valores[0]);
	print_test("Prueba ABB la cantidad de elementos es 0", abb_cantidad(ab) == 0);

	/* Inserta otros 2 valores y no los borra (se destruyen con el ABB) */
	print_test("Prueba ABB insertar claves[1]", abb_guardar(ab, claves[1], valores[1]));
	print_test("Prueba ABB la cantidad de elementos es 1", abb_cantidad(ab) == 1);
	print_test("Prueba ABB obtener claves[1] es valores[1]", abb_obtener(ab, claves[1]) == valores[1]);
	print_test("Prueba ABB obtener claves[1] es valores[1]", abb_obtener(ab, claves[1]) == valores[1]);

	print_test("Prueba ABB insertar claves[2]", abb_guardar(ab, claves[2], valores[2]));
	print_test("Prueba ABB la cantidad de elementos es 2", abb_cantidad(ab) == 2);
	print_test("Prueba ABB obtener claves[2] es valores[2]", abb_obtener(ab, claves[2]) == valores[2]);
	print_test("Prueba ABB obtener claves[2] es valores[2]", abb_obtener(ab, claves[2]) == valores[2]);
	
	abb_destruir(ab);
}

// Función auxiliar de las pruebas de reemplazo.
void prueba_reemplazar_acciones (abb_t* ab, char* claves[], char** valores[]) {
	print_test("Prueba ABB insertar claves[0]", abb_guardar(ab, claves[0], valores[0][0]));
	print_test("Prueba ABB obtener claves[0] es valores[0][0]", abb_obtener(ab, claves[0]) == valores[0][0]);
	print_test("Prueba ABB obtener claves[0] es valores[0][0]", abb_obtener(ab, claves[0]) == valores[0][0]);
	print_test("Prueba ABB insertar claves[1]", abb_guardar(ab, claves[1], valores[1][0]));
	print_test("Prueba ABB obtener claves[1] es valores[1][0]", abb_obtener(ab, claves[1]) == valores[1][0]);
	print_test("Prueba ABB obtener claves[1] es valores[1][0]", abb_obtener(ab, claves[1]) == valores[1][0]);
	print_test("Prueba ABB la cantidad de elementos es 2", abb_cantidad(ab) == 2);

	print_test("Prueba ABB insertar claves[0] con otro valor", abb_guardar(ab, claves[0], valores[0][1]));
	print_test("Prueba ABB obtener claves[0] es valores[0][1]", abb_obtener(ab, claves[0]) == valores[0][1]);
	print_test("Prueba ABB obtener claves[0] es valores[0][1]", abb_obtener(ab, claves[0]) == valores[0][1]);
	print_test("Prueba ABB insertar claves[1] con otro valor", abb_guardar(ab, claves[1], valores[1][1]));
	print_test("Prueba ABB obtener claves[1] es valores[1][1]", abb_obtener(ab, claves[1]) == valores[1][1]);
	print_test("Prueba ABB obtener claves[1] es valores[1][1]", abb_obtener(ab, claves[1]) == valores[1][1]);
	print_test("Prueba ABB la cantidad de elementos es 2", abb_cantidad(ab) == 2);
}

void prueba_reemplazar () {
	printf("\nPRUEBA REEMPLAZAR:\n\n");
	abb_t* ab = abb_crear(strcmp,NULL);
	
	char* claves[] = {"perro", "gato"};
	char* valor1[] = {"guau", "warf"};
	char* valor2[] = {"miau", "meaow"};
	char** valores[2];
	valores[0] = valor1;
	valores[1] = valor2;

	/* Inserta 2 valores y luego los reemplaza */
	prueba_reemplazar_acciones(ab,claves,valores);

	abb_destruir(ab);
}

void prueba_reemplazar_con_destruir () {
	printf("\nPRUEBA REEMPLAZAR CON DESTRUIR:\n\n");
	abb_t* ab = abb_crear(strcmp,free);
	
	char* claves[] = {"perro", "gato"};
	char *valor1[2], *valor2[2];
	char** valores[2];
	
	/* Pide memoria para 4 valores */
	char *valor1a = malloc(10 * sizeof(char)); strcpy(valor1a,"guau");
	char *valor1b = malloc(10 * sizeof(char)); strcpy(valor1a,"warf");
	char *valor2a = malloc(10 * sizeof(char)); strcpy(valor1a,"miau");
	char *valor2b = malloc(10 * sizeof(char)); strcpy(valor1a,"meaow");
	
	/* Los agrupo de a dos */
	valor1[0] = valor1a;
	valor1[1] = valor1b;
	valor2[0] = valor2a;
	valor2[1] = valor2b;
	
	/* Guardo los dos grupos de valores en un solo vector */
	valores[0] = valor1;
	valores[1] = valor2;

	/* Inserta 2 valores y los reemplaza con los otros 2 (debe liberar lo que reemplaza) */
	prueba_reemplazar_acciones(ab,claves,valores);

	/* Se destruye el ABB (se debe liberar lo que quedó dentro) */
	abb_destruir(ab);
}

void prueba_borrar () {
	printf("\nPRUEBA BORRAR:\n\n");
	abb_t* ab = abb_crear(strcmp,NULL);
	
	char *claves[] = {"perro", "gato", "vaca"};
	char *valores[] = {"guau", "miau", "mu"};

	/* Inserta 3 valores y luego los borra */
	print_test("Prueba ABB insertar claves[0]", abb_guardar(ab, claves[0], valores[0]));
	print_test("Prueba ABB insertar claves[1]", abb_guardar(ab, claves[1], valores[1]));
	print_test("Prueba ABB insertar claves[2]", abb_guardar(ab, claves[2], valores[2]));
	
	/*Al borrar cada elemento comprueba que ya no está pero los otros sí. */
	print_test("Prueba ABB borrar claves[2], es valores[2]", abb_borrar(ab, claves[2]) == valores[2]);
	print_test("Prueba ABB borrar claves[2], es NULL", !abb_borrar(ab, claves[2]));
	print_test("Prueba ABB obtener claves[2], es NULL", !abb_obtener(ab, claves[2]));
	print_test("Prueba ABB la cantidad de elementos es 2", abb_cantidad(ab) == 2);

	print_test("Prueba ABB borrar claves[0], es valores[0]", abb_borrar(ab, claves[0]) == valores[0]);
	print_test("Prueba ABB borrar claves[0], es NULL", !abb_borrar(ab, claves[0]));
	print_test("Prueba ABB obtener claves[0], es NULL", !abb_obtener(ab, claves[0]));
	print_test("Prueba ABB la cantidad de elementos es 1", abb_cantidad(ab) == 1);

	print_test("Prueba ABB borrar claves[1], es valores[1]", abb_borrar(ab, claves[1]) == valores[1]);
	print_test("Prueba ABB borrar claves[1], es NULL", !abb_borrar(ab, claves[1]));
	print_test("Prueba ABB obtener claves[1], es NULL", !abb_obtener(ab, claves[1]));
	print_test("Prueba ABB la cantidad de elementos es 0", abb_cantidad(ab) == 0);

	abb_destruir(ab);
}

// Función auxiliar de las pruebas tipo lista y de las de volumen.
void prueba_muchos_elem (abb_t* ab, char* claves[], void* valores[], size_t largo,bool debug) {
	/* Inserta 'largo' parejas en el ABB */
	bool ok = true;
	for (int i = 0; i < largo; i++) {	
		ok = abb_guardar(ab, claves[i], valores[i]);
		if (!ok) break;
	}

	if (debug) print_test("Prueba ABB almacenar muchos elementos", ok);
	if (debug) print_test("Prueba ABB la cantidad de elementos es correcta", abb_cantidad(ab) == largo);

	/* Verifica que devuelva los valores correctos */	
	for (size_t i = 0; i < largo; i++) {
			ok = abb_pertenece(ab, claves[i]);
			if (!ok) break;
			ok = abb_obtener(ab, claves[i]) == valores[i];
			if (!ok) break;
	}

	if (debug) print_test("Prueba ABB pertenece y obtener muchos elementos", ok);
	if (debug) print_test("Prueba ABB la cantidad de elementos es correcta", abb_cantidad(ab) == largo);

	/* Verifica que borre y devuelva los valores correctos */
	for (size_t i = 0; i < largo; i++) {
			ok = (abb_borrar(ab, claves[i]) == valores[i]);
			if (!ok) break;
	}

	if (debug) print_test("Prueba ABB borrar muchos elementos", ok);
	if (debug) print_test("Prueba ABB la cantidad de elementos es 0", abb_cantidad(ab) == 0);
}

void prueba_insertar_borrar_tipo_lista () {
	printf("\nPRUEBA INSERTAR-BORRAR TIPO LISTA:\n\n");
	abb_t* ab = abb_crear(strcmp,NULL);
	
	char  *claves_lista_der[] = {"elefante","gato","jirafa"    ,"leon","mono","perro","pez","sapo" ,"vaca","vinchuca"};
	char *valores_lista_der[] = {"pffff"   ,"miau","jiraaaafaa","MIAU","uhuh","guau","plop","croak","mu"  ,"vinchuuu"};
	char  *claves_lista_izq[] = {"vinchuca","vaca","sapo" ,"pez","perro","mono","leon","jirafa"    ,"gato","elefante"};
	char *valores_lista_izq[] = {"vinchuuu","mu"  ,"croak","plop","guau","uhuh","MIAU","jiraaaafaa","miau","pffff"   };
	
	printf("LISTA HACIA LA DERECHA:\n");
	prueba_muchos_elem(ab,claves_lista_der,(void**)valores_lista_der,10,true);
	printf("\nLISTA HACIA LA IZQUIERDA:\n");
	prueba_muchos_elem(ab,claves_lista_izq,(void**)valores_lista_izq,10,true);
	
	abb_destruir(ab);
}

// Función auxiliar de las pruebas de volumen.
bool datos_crear (char* claves[], int* valores[], size_t largo, size_t l_clave) {
	for (int i = 0; i < largo; i++) {
		claves[i] = (char*) calloc(l_clave,sizeof(char));
		if (!claves[i]) return false;
		sprintf(claves[i], "%d", i+1);
		
		valores[i] = malloc(sizeof(int));
		if (!valores[i]) { free(claves[i]); return false; }
		*(valores[i]) = i+1;
	}
	return true;
}

// Función auxiliar de las pruebas de volumen.
void swap_cad (char **cad1, char **cad2) {
	char* aux = *cad1;
	*cad1 = *cad2;
	*cad2 = aux;
}

// Función auxiliar de las pruebas de volumen.
void swap_int (int **int1, int **int2) {
	int* aux = *int1;
	*int1 = *int2;
	*int2 = aux;
}

// Función auxiliar de las pruebas de volumen.
void datos_mezclar (char* claves[], int* valores[], size_t largo) {
	size_t pos;
	for (size_t i = 0; i < largo; i++) {
		pos = rand() % largo;
		swap_cad (&(claves[i]),&(claves[pos]));
		swap_int (&(valores[i]),&(valores[pos]));
	}
}

// Función auxiliar de las pruebas de volumen.
void claves_destruir (char** claves, size_t tope) {
	for (int i = 0; i < tope; i++) {
		free(claves[i]);
	}
	free(claves);
}

// Función auxiliar de las pruebas de volumen.
int cmp_int (const char* c1, const char* c2) {
	int v1 = atoi(c1);
	int v2 = atoi(c2);
	if (v1 > v2) return 1;
	if (v1 < v2) return -1;
	return 0;
}

void prueba_volumen (int largo, bool debug) {
	printf("\nPRUEBA VOLUMEN con %d elementos:\n\n",largo);
	abb_t* ab = abb_crear(cmp_int,NULL);
	
	const size_t largo_clave = 10;
	char** claves = calloc (largo,sizeof(char*));
	int** valores = calloc (largo,sizeof(int*));
	if (!datos_crear(claves,valores,largo,largo_clave)) {
		free(claves); free(valores); abb_destruir(ab); return;
	}
	datos_mezclar(claves,valores,largo);
	
	prueba_muchos_elem(ab,claves,(void**)valores,largo,debug);

	/* Destruye el ABB y crea uno nuevo que sí libera */
	abb_destruir(ab);
	ab = abb_crear(strcmp,free);

	/* Inserta 'largo' parejas en el ABB */
	bool ok = true;
	for (size_t i = 0; i < largo; i++) {
			ok = abb_guardar(ab, claves[i], valores[i]);
			if (!ok) break;
	}

	claves_destruir(claves,largo);
	free(valores);
	/* Destruye el ABB - debería liberar los enteros */
	abb_destruir(ab);
}

// Funcion auxiliar de la prueba in order.
bool pasar_a_pila (const char* cad, void* val, void* pil) {
	return pila_apilar((pila_t*)pil,(void*)cad);
}

// Funcion auxiliar de la prueba in order.
bool comparar_claves (char* claves[], pila_t* pil) {
	for (int i = 5; i > -1; i--) {
		const char* aux = pila_desapilar(pil);
		if (strcmp(claves[i],aux) != 0) return false;
	}
	return true;
}

void prueba_in_order () {
	printf("\nPRUEBA IN ORDER:\n\n");
	abb_t* ab = abb_crear(strcmp,NULL);
	
	char *claves[] = {"perro", "gato", "vaca", "leon", "sapo" , "vinchuca"};
	char *valores[] = {"guau", "miau", "mu"  , "MIAU", "croak", "vinchuuu"};
	char *claves_ord[] = {"gato", "leon", "perro", "sapo" , "vaca", "vinchuca"};
	
	pila_t* pil1 = pila_crear();
	
	/* Inserto las claves y valores en el ABB */
	for (int i = 0; i < 6; i++) {
		printf ("Prueba ABB insertar clave[%d]",i);
		print_test("", abb_guardar(ab, claves[i], valores[i]));
	}
	
	abb_in_order(ab,NULL,NULL);
	printf ("Se iteró in order sin funcion exitosamente.\n");
	abb_in_order(ab,pasar_a_pila,pil1);
	print_test("Se iteró in order con funcion", comparar_claves(claves_ord,pil1));
	
	pila_destruir(pil1,NULL);
	abb_destruir(ab);
}

void prueba_iterar_abb_vacio () {
	printf("\nPRUEBA ITERAR ABB VACIO:\n\n");
	abb_t* ab = abb_crear(NULL,NULL);
	abb_iter_t* iter = abb_iter_in_crear(ab);
	
	print_test("Prueba ABB iter crear iterador ABB vacio", iter);
	print_test("Prueba ABB iter esta al final", abb_iter_in_al_final(iter));
	print_test("Prueba ABB iter avanzar es false", !abb_iter_in_avanzar(iter));
	print_test("Prueba ABB iter ver actual es NULL", !abb_iter_in_ver_actual(iter));

	abb_iter_in_destruir(iter);
	abb_destruir(ab);
}

void prueba_iterar () {
	printf("\nPRUEBA ITERAR:\n\n");
	abb_t* ab = abb_crear(strcmp,NULL);
	
	char *claves[] = {"perro", "gato", "vaca"};
	char *valores[] = {"guau", "miau", "mu"};

	/* Inserta 3 valores */
	print_test("Prueba ABB insertar clave[0]", abb_guardar(ab, claves[0], valores[0]));
	print_test("Prueba ABB insertar clave[1]", abb_guardar(ab, claves[1], valores[1]));
	print_test("Prueba ABB insertar clave[2]", abb_guardar(ab, claves[2], valores[2]));

	// Prueba de iteración sobre las claves almacenadas.
	abb_iter_t* iter = abb_iter_in_crear(ab);
	const char *clave;
	
	print_test("Prueba ABB iterador esta al final, es false", !abb_iter_in_al_final(iter));

	/* Primer valor */
	clave = abb_iter_in_ver_actual(iter);
	print_test("Prueba ABB iterador ver actual, no es el mismo puntero", clave != claves[1]);
	print_test("Prueba ABB iterador ver actual, el contenido es clave[1]", strcmp(clave,claves[1]) == 0);
	print_test("Prueba ABB iterador avanzar es true", abb_iter_in_avanzar(iter));
	print_test("Prueba ABB iterador esta al final, es false", !abb_iter_in_al_final(iter));
	
	/* Segundo valor */
	clave = abb_iter_in_ver_actual(iter);
	print_test("Prueba ABB iterador ver actual, no es el mismo puntero", clave != claves[0]);
	print_test("Prueba ABB iterador ver actual, el contenido es clave[0]", strcmp(clave,claves[0]) == 0);
	print_test("Prueba ABB iterador avanzar es true", abb_iter_in_avanzar(iter));
	print_test("Prueba ABB iterador esta al final, es false", !abb_iter_in_al_final(iter));

	/* Tercer valor */
	clave = abb_iter_in_ver_actual(iter);
	print_test("Prueba ABB iterador ver actual, no es el mismo puntero", clave != claves[2]);
	print_test("Prueba ABB iterador ver actual, el contenido es clave[2]", strcmp(clave,claves[2]) == 0);
	/* Se anula esta prueba por diferencias de criterios */	
	abb_iter_in_avanzar(iter);
	print_test("Prueba ABB iterador esta al final, es true", abb_iter_in_al_final(iter));

	/* Vuelve a tratar de avanzar, por las dudas */
	print_test("Prueba ABB iterador ver actual, es NULL", !abb_iter_in_ver_actual(iter));
	print_test("Prueba ABB iterador avanzar es false", !abb_iter_in_avanzar(iter));
	print_test("Prueba ABB iterador esta al final, es true", abb_iter_in_al_final(iter));

	abb_iter_in_destruir(iter);
	abb_destruir(ab);
}

void prueba_iterar_volumen (int largo, bool debug) {
	printf("\nPRUEBA ITERAR VOLUMEN con %d elementos:\n\n",largo);
	abb_t* ab = abb_crear(cmp_int,free);
	
	const size_t largo_clave = 10;
	char** claves = calloc (largo,sizeof(char*));
	int** valores = calloc (largo,sizeof(int*));
	if (!datos_crear(claves,valores,largo,largo_clave)) {
		free(claves); free(valores); abb_destruir(ab); return;
	}
	datos_mezclar(claves,valores,largo);

	/* Inserta 'largo' parejas en el ABB */
	bool ok = true;
	for (size_t i = 0; i < largo; i++) {
			ok = abb_guardar(ab, claves[i], valores[i]);
			if (!ok) break;
	}
	
	if (debug) print_test("Prueba ABB almacenar muchos elementos", ok);
	if (debug) print_test("Prueba ABB la cantidad de elementos es correcta", abb_cantidad(ab) == largo);


	abb_iter_t* iter = abb_iter_in_crear(ab);
	bool final = false;
	bool actual = true;
	bool avanzar = true;
	for (size_t i = 0; i < largo; i++) {
		final = abb_iter_in_al_final(iter);
		actual = (abb_iter_in_ver_actual(iter) != NULL);
		avanzar = abb_iter_in_avanzar(iter);
		if ((final) || (!actual) || (!avanzar)) break;
	}
	
	if (debug) print_test("Prueba ABB iterador 'esta al final' con muchos elementos, es false", !final);
	if (debug) print_test("Prueba ABB iterador ver actual de muchos elementos no es NULL", actual);
	if (debug) print_test("Prueba ABB iterador avanzar por muchos elementos es true", avanzar);
	
	abb_iter_in_destruir(iter);
	/* Destruye el ABB - debería liberar los enteros */
	abb_destruir(ab);
	
	claves_destruir(claves,largo);
	free(valores);
}
	
/* ******************************************************************
 *                        PROGRAMA PRINCIPAL						*
 * *****************************************************************/

int main (int argc, char** argv) {
	printf ("\nCOMIENZO DE PRUEBAS ABB.\n");
	
	if (argc < 2) {
		prueba_crear_abb_vacio ();
		prueba_insertar();
		prueba_reemplazar();
		prueba_reemplazar_con_destruir();
		prueba_borrar ();
		prueba_insertar_borrar_tipo_lista ();
		prueba_volumen(5000, true);
		prueba_in_order();
		prueba_iterar_abb_vacio();
		prueba_iterar();
		prueba_iterar_volumen(5000, true);
	} else {
	    size_t largo = atoi(argv[1]);
    	prueba_volumen(largo, true);
    	prueba_iterar_volumen(largo, true);
	}
	
	printf ("\nFIN DE PRUEBAS ABB.\n\n");
	return 0;
}
